/******************************************************************************
 * Copyright 2023 The Firmament Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *****************************************************************************/
#include <firmament.h>
#include "hal/adc/adc.h"
#include "device.h"
#include "f28p65x_device.h"

#define ADC_CONVERSION_TIMEOUT_MS 2

static struct adc_device adc0;
static struct rt_completion convert_cplt;

__interrupt void adca1_isr(void)
{
    /* enter interrupt */
    rt_interrupt_enter();;

    if (ADC_getInterruptStatus(ADCA_BASE, ADC_INT_NUMBER1)) {

        /* inform the completion of adc convertion */
        rt_completion_done(&convert_cplt);

        ADC_clearInterruptStatus(ADCA_BASE, ADC_INT_NUMBER1);
    }

    if (ADC_getInterruptStatus(ADCA_BASE, ADC_INT_NUMBER2)) {
        ADC_clearInterruptStatus(ADCA_BASE, ADC_INT_NUMBER2);
    }

    if (ADC_getInterruptStatus(ADCA_BASE, ADC_INT_NUMBER3)) {
        ADC_clearInterruptStatus(ADCA_BASE, ADC_INT_NUMBER3);
    }

    if (ADC_getInterruptStatus(ADCA_BASE, ADC_INT_NUMBER4)) {
        ADC_clearInterruptStatus(ADCA_BASE, ADC_INT_NUMBER4);
    }

    /* leave interrupt */
    rt_interrupt_leave();
}

static rt_err_t adc_hw_init(uint32_t adc_base)
{
    // Config ADC Clock
    if (ADCA_BASE == adc_base) {
        SysCtl_enablePeripheral(SYSCTL_PERIPH_CLK_ADCA);
    } else if (ADCB_BASE == adc_base) {
        SysCtl_enablePeripheral(SYSCTL_PERIPH_CLK_ADCB);
    } else if (ADCC_BASE == adc_base) {
        SysCtl_enablePeripheral(SYSCTL_PERIPH_CLK_ADCC);
    } else {
        return RT_EINVAL;
    }

#if 0
    //
    // ADC Initialization: Write ADC configurations and power up the ADC
    //
    // Set the analog voltage reference selection and ADC module's offset trims.
    // This function sets the analog voltage reference to internal (with the reference voltage
    // of 1.65V or 2.5V) or external for ADC which is same as ASysCtl APIs.
    //
    ADC_setVREF(adc_base, ADC_REFERENCE_EXTERNAL, ADC_REFERENCE_3_3V);
    //
    // Configures the analog-to-digital converter module prescaler.
    //
    ADC_setPrescaler(adc_base, ADC_CLK_DIV_2_0);
    //
    // Sets the timing of the end-of-conversion pulse
    //
    ADC_setInterruptPulseMode(adc_base, ADC_PULSE_END_OF_CONV);
    //
    // Powers up the analog-to-digital converter core.
    //
    ADC_enableConverter(adc_base);
    //
    // Delay for 1ms to allow ADC time to power up
    //
    DEVICE_DELAY_US(500);
    //
    // SOC Configuration: Setup ADC EPWM channel and trigger settings
    //
    // Disables SOC burst mode.
    //
    ADC_disableBurstMode(adc_base);
    //
    // Sets the priority mode of the SOCs.
    //
    ADC_setSOCPriority(adc_base, ADC_PRI_ALL_ROUND_ROBIN);

    //
    // Start of Conversion 0 Configuration
    //
    //
    // Configures a start-of-conversion (SOC) in the ADC and its interrupt SOC trigger.
    //      SOC number      : 0
    //      Trigger         : ADC_TRIGGER_SW_ONLY
    //      Channel         : ADC_CH_ADCIN0
    //      Sample Window   : 8 SYSCLK cycles
    //      Interrupt Trigger: ADC_INT_SOC_TRIGGER_NONE
    //
    ADC_setupSOC(adc_base, ADC_SOC_NUMBER0, ADC_TRIGGER_SW_ONLY, ADC_CH_ADCIN6, ADC_SAMPLE_WINDOW_10);
    ADC_setInterruptSOCTrigger(adc_base, ADC_SOC_NUMBER0, ADC_INT_SOC_TRIGGER_NONE);

    // bind on ADC_INT_NUMBER(1~4) - INT_ADCA1
    ADC_enableInterrupt(adc_base, ADC_INT_NUMBER1);
    ADC_setInterruptSource(adc_base, ADC_INT_NUMBER1, ADC_SOC_NUMBER0);
    // ADC_enableContinuousMode(adc_base, ADC_INT_NUMBER1);

    Interrupt_enable(INT_ADCA1);
    Interrupt_register(INT_ADCA1, adca1_isr);
#endif

    return RT_EOK;
}

static rt_err_t enable(adc_dev_t adc_dev, uint8_t enable)
{
    uint32_t adc_base = (uint32_t)adc_dev->parent.user_data;

    if (enable == ADC_CMD_ENABLE) {
        // enable ADC Clock
        if (ADCA_BASE == adc_base) {
            SysCtl_enablePeripheral(SYSCTL_PERIPH_CLK_ADCA);
        } else if (ADCB_BASE == adc_base) {
            SysCtl_enablePeripheral(SYSCTL_PERIPH_CLK_ADCB);
        } else if (ADCC_BASE == adc_base) {
            SysCtl_enablePeripheral(SYSCTL_PERIPH_CLK_ADCC);
        } else {
            return RT_EINVAL;
        }

        /* the ADC needs a stabilization time of tSTAB 
         * before it starts converting accurately 
         */
        sys_msleep(1);

    } else if (enable == ADC_CMD_DISABLE) {
        // disable ADC Clock
        if (ADCA_BASE == adc_base) {
            SysCtl_disablePeripheral(SYSCTL_PERIPH_CLK_ADCA);
        } else if (ADCB_BASE == adc_base) {
            SysCtl_disablePeripheral(SYSCTL_PERIPH_CLK_ADCB);
        } else if (ADCC_BASE == adc_base) {
            SysCtl_disablePeripheral(SYSCTL_PERIPH_CLK_ADCC);
        } else {
            return RT_ERROR;
        }
        
    } else {
        return RT_EINVAL;
    }

    return RT_EOK;
}

static rt_err_t measure(adc_dev_t adc_dev, uint32_t channel, uint32_t* mVolt)
{
    uint32_t adc_base = (uint32_t)adc_dev->parent.user_data;
    uint32_t adc_channel;
    uint16_t adcData;

    switch (channel) {
    case 0: /* BAT1_VOL */
        adc_channel = ADC_CH_ADCIN6;
        break;
    case 1: /* TODO BAT1_CURRENT */
        // adc_channel = ADC_CH_ADCIN6;
        // break;
    default:
        return RT_EINVAL;
    }

#if 0
    /* ADC routine channel config */
    ADC_setupSOC(adc_base, ADC_SOC_NUMBER0, ADC_TRIGGER_SW_ONLY, adc_channel, ADC_SAMPLE_WINDOW_10);

    /* ADC software trigger enable */
    ADC_forceMultipleSOC(adc_base, ADC_FORCE_SOC0);

    if (rt_completion_wait(&convert_cplt, TICKS_FROM_MS(ADC_CONVERSION_TIMEOUT_MS)) != RT_EOK) {
        return RT_ERROR;
    }
#endif

    adcData = ADC_readResult(adc_base, ADC_SOC_NUMBER0);
    *mVolt = adcData * 3300 / 4095;

    return RT_EOK;
}

/* usart driver operations */
static const struct adc_ops _adc_ops = {
    .enable = enable,
    .measure = measure
};

rt_err_t drv_adc_init(void)
{
    RT_CHECK(adc_hw_init(ADCA_BASE));

    rt_completion_init(&convert_cplt);

    adc0.parent.user_data = (void*)ADCA_BASE;
    adc0.ops = &_adc_ops;

    return hal_adc_register(&adc0, "adc0", RT_DEVICE_FLAG_RDONLY, adc0.parent.user_data);
}